
/*
*********************************************************************************************************
*                                            INCLUDE FILES
*********************************************************************************************************
*/

#define    MB_RTU_MODULE
#include  <mb.h>

#define DBG_LVL     DBG_INFO
#define DBG_TAG     "mb.rtu"
#include <rtdbg.h>


/*
*********************************************************************************************************
*                                              MB_RtuCh()
*
* Description : This function must be called after calling MB_Init() to initialize each of the Modbus
*               channels in your system.
*
* Argument(s) : node_addr     is the Modbus node address that the channel is assigned to.
*
*               master_slave  specifies whether the channel is a MODBUS_MASTER or a MODBUS_SLAVE
*
*               rx_timeout    amount of time Master will wait for a response from the slave.
*
*               modbus_mode   specifies the type of modbus channel.  The choices are:
*                             MODBUS_MODE_ASCII
*                             MODBUS_MODE_RTU
*
*               port_nbr      is the UART port number associated with the channel
*
*               baud          is the desired baud rate
*
*               parity        is the UART's parity setting:
*                             MODBUS_PARITY_NONE
*                             MODBUS_PARITY_ODD
*                             MODBUS_PARITY_EVEN
*
*               bits          UART's number of bits (7 or 8)
*
*               stops         Number of stops bits (1 or 2)
*
*               wr_en         This argument determines whether a Modbus WRITE request will be accepted.
*                             The choices are:
*                             MODBUS_WR_EN
*                             MODBUS_WR_DIS
*
* Return(s)   : none.
*
* Caller(s)   : Application.
*
* Note(s)     : none.
*********************************************************************************************************
*/
#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)

static rt_err_t MB_Rtu_RxIndicate(rt_device_t dev, rt_size_t size);

MODBUS_CH* MB_RtuCh(CPU_INT08U node_addr,
                    CPU_INT08U master_slave,
                    CPU_INT32U rx_timeout,
                    CPU_INT08U port_nbr,
                    CPU_INT08U tx_pin,
                    CPU_INT32U baud,
                    CPU_INT08U bits,
                    CPU_INT08U parity,
                    CPU_INT08U stops,
                    CPU_INT08U wr_en)
{
    MODBUS_CH *pch;
#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
    CPU_INT16U cnts;
#endif

    if (MB_ChCtr < MODBUS_CFG_MAX_CH)
    {
        pch = &MB_ChTbl[MB_ChCtr];
        MB_MasterTimeoutSet(pch, rx_timeout);
        MB_NodeAddrSet(pch, node_addr);
        MB_ModeSet(pch, master_slave, MODBUS_MODE_RTU);
        MB_WrEnSet(pch, wr_en);

        pch->tx_pin = tx_pin;

#ifdef RT_USING_PIN
        rt_pin_mode(pch->tx_pin, PIN_MODE_OUTPUT);
        rt_pin_write(pch->tx_pin, PIN_LOW);
#else
#warning "you must enable RT_USING_PIN"
#endif

#if (MODBUS_CFG_RTU_EN == DEF_ENABLED) || (MODBUS_CFG_ASCII_EN == DEF_ENABLED)
        {
            char name[32];
            struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
            rt_snprintf(name, sizeof(name), "uart%d",port_nbr);
            struct rt_device *serial = rt_device_find(name);
            if(serial == RT_NULL)
            {
                LOG_E("com device:%s can't be found",name);
                return ((MODBUS_CH*) 0);
            }
            pch->com_dev = (rt_device_t)serial;

            if(RT_EOK != rt_device_open((rt_device_t)serial, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX))
            {
                LOG_E("com device:%s can't be opened",name);
                return ((MODBUS_CH*) 0);
            }

            config.baud_rate = baud;
            config.data_bits = bits;
            switch (parity)
            {
                case MODBUS_PARITY_EVEN:
                    config.parity = PARITY_EVEN;
                break;
                case MODBUS_PARITY_ODD:
                    config.parity = PARITY_ODD;
                break;
                case MODBUS_PARITY_NONE:
                default:
                    config.parity = PARITY_NONE;
                break;
            }
            switch (stops)
            {
                case 2:
                    config.stop_bits = STOP_BITS_2;
                break;
                case 1:
                default:
                    config.stop_bits = STOP_BITS_1;
                break;
            }
            rt_device_control((rt_device_t)serial, RT_DEVICE_CTRL_CONFIG, (void *)&config);
            rt_device_set_rx_indicate((rt_device_t)serial, MB_Rtu_RxIndicate);
        }
#endif
#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
        if (pch->MasterSlave == MODBUS_MASTER)
        {
            pch->RTU_TimeoutEn = DEF_FALSE;
        }

        cnts = ((CPU_INT32U) MB_RTU_Freq * 5L * 10L) / baud; /* Freq * 5 char * 10 bits/char * 1/BaudRate          */
        if (cnts <= 1)
        {
            cnts = 2;
        }
        pch->RTU_TimeoutCnts = cnts;
        pch->RTU_TimeoutCtr = cnts;
#endif
        MB_ChCtr++;
        return (pch);
    }
    else
    {
        return ((MODBUS_CH*) 0);
    }
}
#endif /* (MODBUS_CFG_RTU_EN == DEF_ENABLED) || (MODBUS_CFG_ASCII_EN == DEF_ENABLED)    */




/*
*********************************************************************************************************
*                                            MB_RTU_RxByte()
*
* Description : A byte has been received from a serial port.  We just store it in the buffer for processing
*               when a complete packet has been received.
*
* Argument(s) : pch         Is a pointer to the Modbus channel's data structure.
*
*               rx_byte     Is the byte received.
*
* Return(s)   : none.
*
* Caller(s)   : MB_RxByte().
*
* Note(s)     : none.
*********************************************************************************************************
*/

#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)

static rt_err_t MB_Rtu_RxIndicate(rt_device_t dev, rt_size_t size)
{
    struct rt_serial_device *serial = (struct rt_serial_device *)dev;
    CPU_INT08U   ch;
    MODBUS_CH  *pch = (MODBUS_CH  *)RT_NULL;
    rt_uint8_t  rx_byte = 0;

    for (ch = 0; ch < MODBUS_CFG_MAX_CH; ++ch)
    {
        if(MB_ChTbl[ch].com_dev == dev)
        {
            pch = &MB_ChTbl[ch];
            break;
        }
    }

    if(pch != RT_NULL)
    {
        while (size--)
        {
            if (1 == rt_device_read(dev, 0, &rx_byte, 1))
            {
                MB_RTU_RxByte(pch, rx_byte);
            }
        }
        return RT_EOK;
    }

    return -RT_ERROR;
}



void MB_RTU_RxByte(MODBUS_CH *pch, CPU_INT08U rx_byte)
{
    MB_RTU_TmrReset(pch);                                       /* Reset the timeout timer on a new character             */
#if (MODBUS_CFG_MASTER_EN == DEF_ENABLED)
    if (pch->MasterSlave == MODBUS_MASTER)
    {
        pch->RTU_TimeoutEn = MODBUS_TRUE;
    }
#endif
    if (pch->RxBufByteCtr < MODBUS_CFG_BUF_SIZE)
    {                                                           /* No, add received byte to buffer                        */
        pch->RxCtr++;                                           /* Increment the number of bytes received                 */
        *pch->RxBufPtr++ = rx_byte;
        pch->RxBufByteCtr++;                                    /* Increment byte counter to see if we have Rx activity   */
    }
}
#endif


/*
*********************************************************************************************************
*                                              MB_RTU_Rx()
*
* Description : Parses a Modbus RTU packet and processes the request if the packet is valid.
*
* Argument(s) : pch         Is a pointer to the Modbus channel's data structure.
*
* Return(s)   : DEF_TRUE    If all checks pass.
*               DEF_FALSE   If any checks fail.
*
* Caller(s)   : MBM_RxReply(),
*               MBS_RTU_Task().
*
* Note(s)     : none.
*********************************************************************************************************
*/

#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
CPU_BOOLEAN  MB_RTU_Rx (MODBUS_CH  *pch)
{
    CPU_INT08U  *prx_data;
    CPU_INT08U  *pmsg;
    CPU_INT16U   rx_size;
    CPU_INT16U   crc;


    pmsg    = &pch->RxBuf[0];
    rx_size =  pch->RxBufByteCtr;
    if (rx_size >= MODBUS_RTU_MIN_MSG_SIZE) {         /* Is the message long enough?                        */
        if (rx_size <= MODBUS_CFG_BUF_SIZE) {
            prx_data    = &pch->RxFrameData[0];
            *prx_data++ = *pmsg++;                    /* Transfer the node address                          */
            rx_size--;

            *prx_data++ = *pmsg++;                    /* Transfer the function code                         */
            rx_size--;

            pch->RxFrameNDataBytes = 0;               /* Transfer the data                                  */
            while (rx_size > 2) {
                *prx_data++ = *pmsg++;
                pch->RxFrameNDataBytes++;
                rx_size--;
            }

            crc              = (CPU_INT16U)*pmsg++;   /* Transfer the CRC over.  It's LSB first, then MSB.  */
            crc             += (CPU_INT16U)*pmsg << 8;
            pch->RxFrameCRC  = crc;
            return (DEF_TRUE);
        } else {
            return (DEF_FALSE);
        }
    } else {
        return (DEF_FALSE);
    }
}
#endif


/*
*********************************************************************************************************
*                                              MB_RTU_Tx()
*
* Description : A MODBUS message is formed into a buffer and sent to the appropriate communication port.
*               The actual reply is taken from the given MODBUS Frame.
*
* Argument(s) : pch      Is a pointer to the Modbus channel's data structure.
*
* Return(s)   : none.
*
* Caller(s)   : MBM_TxCmd(),
*               MBS_RTU_Task().
*
* Note(s)     : none.
*********************************************************************************************************
*/

#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
void  MB_RTU_Tx (MODBUS_CH  *pch)
{
    CPU_INT08U  *ptx_data;
    CPU_INT08U  *pbuf;
    CPU_INT08U   i;
    CPU_INT16U   tx_bytes;
    CPU_INT16U   crc;


    tx_bytes  = 0;
    pbuf      = &pch->TxBuf[0];                                    /* Point to the beginning of the output buffer.             */
    ptx_data  = &(pch->TxFrameData[0]);
    i         = (CPU_INT08U)pch->TxFrameNDataBytes + 2;            /* Include the actual data in the buffer                    */
    while (i > 0) {
        *pbuf++ = *ptx_data++;
        tx_bytes++;
        i--;
    }
    crc               = MB_RTU_TxCalcCRC(pch);
    *pbuf++           = (CPU_INT08U)(crc & 0x00FF);                /* Add in the CRC checksum.  Low byte first!                */
    *pbuf             = (CPU_INT08U)(crc >> 8);
    tx_bytes         += 2;
    pch->TxFrameCRC   = crc;                                       /* Save the calculated CRC in the channel                   */
    pch->TxBufByteCtr = tx_bytes;
    // send
    {                                                              /* Send it out the communication driver.                    */
        pch->TxBufPtr = &pch->TxBuf[0];
        if(pch->com_dev != RT_NULL)
        {
#ifdef RT_USING_PIN
            rt_pin_write(pch->tx_pin, PIN_HIGH);
#else
#warning "you must enable RT_USING_PIN"
#endif
            if(pch->TxBufByteCtr != rt_device_write(pch->com_dev, 0, pch->TxBufPtr, pch->TxBufByteCtr))
            {
                LOG_E("send failed");
            }
            pch->TxCtr = pch->TxBufByteCtr;
            pch->TxBufByteCtr = 0;
            pch->TxBufPtr = &pch->TxBuf[0];                         /* Reset at beginning of buffer                       */
            //sent com in recv...
#ifdef RT_USING_PIN
            rt_pin_write(pch->tx_pin, PIN_LOW);
#else
#warning "you must enable RT_USING_PIN"
#endif

#if (MODBUS_CFG_MASTER_EN == DEF_ENABLED)
            if (pch->MasterSlave == MODBUS_MASTER)
            {
#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
                pch->RTU_TimeoutEn = MODBUS_FALSE;                  /* Disable RTU timeout timer until we start receiving */
#endif
                pch->RxBufByteCtr  = 0;                             /* Flush Rx buffer                                    */
            }
#endif

        }
        else
        {
            LOG_E("com device NULL");
        }
    }

}
#endif


/*
*********************************************************************************************************
*                                           MB_RTU_TmrReset()
*
* Description : This function is called when a byte a received and thus, we reset the RTU timeout timer value
*               indicating that we are not done receiving a complete RTU packet.
*
* Argument(s) : none.
*
* Return(s)   : none.
*
* Caller(s)   : MB_RTU_TmrResetAll().
*
* Note(s)     : none.
*********************************************************************************************************
*/

#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
void  MB_RTU_TmrReset (MODBUS_CH  *pch)
{
    pch->RTU_TimeoutCtr = pch->RTU_TimeoutCnts;
}
#endif


/*
*********************************************************************************************************
*                                           MB_RTU_TmrResetAll()
*
* Description : This function is used to reset all the RTU timers for all Modbus channels.
*
* Argument(s) : none.
*
* Return(s)   : none.
*
* Caller(s)   : MB_RTU_TmrInit().
*
* Note(s)     : none.
*********************************************************************************************************
*/

#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
void  MB_RTU_TmrResetAll (void)
{
    CPU_INT08U   ch;
    MODBUS_CH   *pch;


    pch = &MB_ChTbl[0];
    for (ch = 0; ch < MODBUS_CFG_MAX_CH; ch++) {
        if (pch->Mode == MODBUS_MODE_RTU) {
            MB_RTU_TmrReset(pch);
        }
        pch++;
    }
}
#endif


/*
*********************************************************************************************************
*                                           MB_RTU_TmrUpdate()
*
* Description : This function is called when the application supplied RTU framing timer expires.
*
* Argument(s) : none.
*
* Return(s)   : none.
*
* Caller(s)   : MB_RTU_TmrISR_Handler().
*
* Note(s)     : none.
*********************************************************************************************************
*/

#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
void  MB_RTU_TmrUpdate (void)
{
    CPU_INT08U   ch;
    MODBUS_CH   *pch;

    pch = &MB_ChTbl[0];
    for (ch = 0; ch < MODBUS_CFG_MAX_CH; ch++) {
        if (pch->Mode == MODBUS_MODE_RTU) {
            if (pch->RTU_TimeoutEn == DEF_TRUE) {
                if (pch->RTU_TimeoutCtr > 0) {
                    pch->RTU_TimeoutCtr--;
                    if (pch->RTU_TimeoutCtr == 0) {
#if (MODBUS_CFG_RTU_EN == DEF_ENABLED)
                        if (pch->MasterSlave == MODBUS_MASTER) {
                            pch->RTU_TimeoutEn = DEF_FALSE;
                        }
#endif
                        MB_OS_RxSignal(pch);          /* RTU Timer expired for this Modbus channel         */
                    }
                }
            } else {
                pch->RTU_TimeoutCtr = pch->RTU_TimeoutCnts;
            }
        }
        pch++;
    }
}
#endif
